﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LAeMail.Infrastructure;
using System.IO;
using System.Windows.Threading;
using System.Windows;
using System.Threading;
using System.Net.Mail;
using System.Net;
using System.Text.RegularExpressions;

namespace LAeMail
{
    public class MailManager : PropertyHelpersBase 
    {
        #region Private
        private const int threadPoolCount = 8;

        private List<String> emailAddresses = new List<string>();
        private String bodyText = String.Empty;
        private bool bodyIsHtml = false;
        private String subject = String.Empty;

        private Action onRunning { get; set; }
        private Action onComplete { get; set; }
        private Action<LogLevel, string> onNotify { get; set; }
        private Dispatcher dispatcher { get; set; }

        private Thread mailQueueThread { get; set; }
        private ManualResetEvent[] resetEvents = new ManualResetEvent[threadPoolCount];

        #endregion

        #region Properties
        public int EmailAddressCount
        {
            get { return Get(() => EmailAddressCount); }
            set { Set(() => EmailAddressCount, value); }
        }

        public int TotalEmails
        {
            get { return Get(() => TotalEmails); }
            set { Set(() => TotalEmails, value); }
        }

        public int SentOk
        {
            get { return Get(() => SentOk); }
            set { Set(() => SentOk, value); }
        }

        public int Errors
        {
            get { return Get(() => Errors); }
            set { Set(() => Errors, value); }
        }

        public bool Running
        {
            get { return Get(() => Running); }
            set { Set(() => Running, value); }
        }

        public bool Spinning
        {
            get { return Get(() => Spinning); }
            set { Set(() => Spinning, value); }
        }
        #endregion

        public MailManager(Action onRunning, Action onComplete, Action<LogLevel, string> onNotify)
        {
            TotalEmails = 0;
            SentOk = 0;
            Errors = 0;
            Running = false;
            Spinning = false;

            this.onRunning = onRunning;
            this.onComplete = onComplete;
            this.onNotify = onNotify;
            this.dispatcher = Application.Current.Dispatcher;
        }

        #region Loading 
        public bool Load(String subject, String bodyTextPath, String recipientListPath)
        {
            TotalEmails = SentOk = Errors = EmailAddressCount = 0;
            emailAddresses.Clear();
            bodyText = String.Empty;
            bodyIsHtml = false;
            this.subject = subject;

            if (String.IsNullOrEmpty(subject))
            {
                callbackOnNotify(LogLevel.Error, Properties.Resources.ERROR_SubjectError);
                return false;
            }

            if (!LoadRecipients(recipientListPath))
                return false;

            if (!LoadBodyText(bodyTextPath))
                return false;

            EmailAddressCount = emailAddresses.Count;
            return true;
        }

        private bool LoadRecipients(String recipientListPath)
        {
            if (File.Exists(recipientListPath) == false)
            {
                callbackOnNotify(LogLevel.Error, Properties.Resources.ERROR_RecipientListPathError);
                return false;
            }

            try
            {
                using (StreamReader reader = new StreamReader(recipientListPath))
                {
                    String emailAddress = null;
                    while ((emailAddress = reader.ReadLine()) != null)
                    {
                        emailAddress = emailAddress.Trim();
                        emailAddress = emailAddress.ToLower();

                        if (!String.IsNullOrEmpty(emailAddress) && !emailAddresses.Contains(emailAddress))
                            emailAddresses.Add(emailAddress);
                    }
                }
            }
            catch
            {
                callbackOnNotify(LogLevel.Error, Properties.Resources.ERROR_RecipientListPathError);
                return false;
            }

            if (emailAddresses.Count == 0)
            {
                callbackOnNotify(LogLevel.Error, Properties.Resources.ERROR_RecipientListEmpty);
                return false;
            }
            return true;
        }

        private bool LoadBodyText(String bodyTextPath)
        {
            if (File.Exists(bodyTextPath) == false)
            {
                callbackOnNotify(LogLevel.Error, Properties.Resources.ERROR_BodyTextPathError);
                return false;
            }

            try
            {
                using (StreamReader reader = new StreamReader(bodyTextPath))
                {
                    bodyText = reader.ReadToEnd();
                    bodyText.Trim();
                }
            }
            catch
            {
                callbackOnNotify(LogLevel.Error, Properties.Resources.ERROR_BodyTextPathError);
                return false;
            }

            if (String.IsNullOrEmpty(bodyText))
            {
                callbackOnNotify(LogLevel.Error, Properties.Resources.ERROR_BodyTextEmpty);
                return false;
            }

            Regex HTMLTag = new Regex(@"(<\/?[^>]+>)");

            foreach (Match Tag in HTMLTag.Matches(bodyText))
            {
                if (Tag.Value.ToLower().StartsWith("<html"))
                {
                    bodyIsHtml = true;
                    break;
                }

                if (Tag.Value.ToLower().StartsWith("<head"))
                {
                    bodyIsHtml = true;
                    break;
                }

                if (Tag.Value.ToLower().StartsWith("<body"))
                {
                    bodyIsHtml = true;
                    break;
                }
            }
            return true;
        }
        #endregion

        #region Callbacks
        private void callbackOnRunning()
        {
            if (dispatcher != null && onRunning != null)
                dispatcher.BeginInvoke(onRunning);
        }

        private void callbackOnComplete()
        {
            if (dispatcher != null && onComplete != null)
                dispatcher.BeginInvoke(onComplete);
        }

        private void callbackOnNotify(LogLevel level, String lastError)
        {
            if (dispatcher != null && onNotify != null)
                dispatcher.BeginInvoke(onNotify, new object[] {level, lastError});
        }
        #endregion

        #region Processing Mail Queue
        public void Run()
        {
            Spinning = true;
            callbackOnRunning();

            mailQueueThread = new Thread(new ThreadStart(() =>
            {
                try
                {
                    for (int i = 0; i < threadPoolCount; i++)
                    {
                        resetEvents[i] = new ManualResetEvent(false);
                        ThreadPool.QueueUserWorkItem(new WaitCallback(processQueue), (object)i);
                    }
                    Running = true;
                    Spinning = false;
                    WaitHandle.WaitAll(resetEvents);
                }
                catch (ThreadInterruptedException)
                {
                    Spinning = false;
                    callbackOnNotify(LogLevel.Warning, Properties.Resources.WARNING_SendingInterrupted);
                }
                catch (Exception ex)
                {
                    Spinning = false;
                    callbackOnNotify(LogLevel.Error, String.Format(Properties.Resources.ERROR_QueueProcessing, ex.Message));
                }
                finally
                {
                    Spinning = false;
                    Running = false;
                    callbackOnComplete();
                }
            }));

            mailQueueThread.Start();
        }

        public void Cancel()
        {
            if (Running)
            {
                Spinning = true;
                mailQueueThread.Interrupt();
            }
        }

        private void processQueue(object o)
        {
            int index = (int)o;
            String address = null;
            int port = 25;
            Int32.TryParse(Properties.Settings.Default.EmailPort, out port);

            using (SmtpClient smtp = new SmtpClient(Properties.Settings.Default.EmailServer, port))
            {
                if (!String.IsNullOrEmpty(Properties.Settings.Default.EmailUser))
                    smtp.Credentials = new NetworkCredential(Properties.Settings.Default.EmailUser, Properties.Settings.Default.EmailPassword);

                while (Running && !String.IsNullOrEmpty(address = getNextAddress()))
                {
                    try
                    {
                        MailMessage mail = new MailMessage();
                        mail.From = new MailAddress(Properties.Settings.Default.EmailSender, Properties.Settings.Default.EmailFriendlyName);
                        mail.To.Add(new MailAddress(address));
                        mail.Body = bodyText;
                        mail.IsBodyHtml = bodyIsHtml;
                        mail.Subject = subject;

                        callbackOnNotify(LogLevel.Info, String.Format(Properties.Resources.INFO_SendingToAddress, index, address));
                        smtp.Send(mail);

                        lock (this)
                        {
                            TotalEmails++;
                            SentOk++;
                        }
                    }
                    catch (Exception ex)
                    {
                        lock (this)
                        {
                            TotalEmails++;
                            Errors++;
                        }
                        callbackOnNotify(LogLevel.Error, String.Format(Properties.Resources.ERROR_SendingToAddress, index, address, ex.Message));
                    }
                }
            }
            resetEvents[index].Set();
        }

        private String getNextAddress()
        {
            String address = String.Empty;

            lock (emailAddresses)
            {
                if (emailAddresses.Count != 0)
                {
                    address = emailAddresses[0];
                    emailAddresses.RemoveAt(0);
                }
            }
            return address;
        }
        #endregion
    }
}
